package com.xy.iot.netty;

import cn.hutool.core.util.ObjectUtil;
import com.xy.iot.common.message.BaseMessage;
import com.xy.iot.common.result.message.ClientPingMessageData;
import com.xy.iot.common.utils.CacheUtils;
import com.xy.iot.netty.channel.BaseChannel;
import com.xy.iot.netty.connect.BaseClientConnect;
import com.xy.iot.netty.device.DeviceClientChannel;
import com.xy.iot.netty.handle.ByteClientMessageDecoder;
import com.xy.iot.netty.handle.ByteClientMessageEncoder;
import com.xy.iot.netty.handle.DelimiterMessageDecoder;
import com.xy.iot.netty.handle.ExceptionClientHandler;
import com.xy.iot.netty.listener.Listener;
import com.xy.iot.netty.listener.ListenerClientHandler;
import com.xy.iot.netty.util.SendUtil;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.timeout.IdleStateEvent;
import io.netty.handler.timeout.IdleStateHandler;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;

import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;

/**
* @author: niuniu
* @Date: 2024/6/10 22:06
* @Description: 客户端启动类
*/
@Slf4j
public class StartClient extends ChannelInitializer<SocketChannel> {

   /**
    * 设备分组
    */
   private final String deviceGroup;
   /**
    * 线程数
    */
   private final int threadNum;
   /**
    * 监听器
    */
   private final ListenerClientHandler handler;
    /**
     * 心跳检测超时时间(单位：毫秒)
     */
    @Getter
    private final Integer idleTimeout;
    /**
     * 地址集合
     */
   private final Set<InetSocketAddress> addressSet = new HashSet<>();
    /**
     * 工作分组
     */
    private final Map<String, EventLoopGroup> workerGroupMap = new ConcurrentHashMap<>();
    /**
     * 连接管理
     */
   private final Map<String, BaseClientConnect> cmMap = new ConcurrentHashMap<>();

    /**
     *  监听器管理
     */
   private final Map<String, ChannelFuture> futureMap = new ConcurrentHashMap<>();
    private final Class<? extends BaseMessage> baseMessageData;
    /**
     * 构造方法
     * @param deviceGroup 设备分组
     * @param messageData 消息数据
     */
   public StartClient(String deviceGroup, Class<? extends BaseMessage> messageData){
       this(deviceGroup, 0, 60000, messageData);
   }
    /**
     * 构造方法
     * @param deviceGroup 设备分组
     * @param nThread 线程数
     * @param heartBeatTime 心跳检测超时时间(单位：毫秒)
     * @param messageData 消息数据
     */
   public StartClient(String deviceGroup, int nThread, int heartBeatTime, Class<? extends BaseMessage> messageData){
       this.deviceGroup = deviceGroup;
       this.threadNum = nThread;
       this.idleTimeout = heartBeatTime;
       this.baseMessageData = messageData;
       this.handler = new ListenerClientHandler();//这个地方可以自己在创建一个

   }

   /**
    * 连接服务端
    * @param serverHost 服务端地址
    * @param serverPort 服务端端口
    * @param reconnectTime 重连时间
    * @return
    * @throws Exception
    */
   public ChannelFuture connect(String serverHost, int serverPort, int reconnectTime) throws Exception {
       EventLoopGroup workerGroup = new NioEventLoopGroup(threadNum);//创建线程组
       workerGroupMap.put(serverHost, workerGroup);//添加到集合中
       Bootstrap boot = new Bootstrap()//创建bootstrap对象
               .group(workerGroup)//绑定线程组
               .channel(NioSocketChannel.class)//指定nioSocketChannel通道
               .handler(this)//绑定handler
               ;
       InetSocketAddress address = InetSocketAddress.createUnresolved(serverHost, serverPort);//创建socket地址对象
       addressSet.add(address);//添加到集合中
       //创建连接
       BaseClientConnect clientConnect = new BaseClientConnect(boot, reconnectTime){
           /**
            * 连接成功后执行
            */
           @Override
           public void afterSuccess() {
               log.info("连接成功回调,服务端：{}:{}", serverHost, serverPort);
               //sender().registerGroup(serverHost, deviceGroup);
           }
       };
       cmMap.put(serverHost, clientConnect);//添加到集合中
       ChannelFuture future = clientConnect.connect(address);//创建连接，返回监听器
       futureMap.put(serverHost, future);//添加监听器到集合中
       log.info("连接服务端,服务端：{}:{}", serverHost, serverPort);
       return future;
   }
    /**
     * 停止服务
     * @param serverHost
     * @return
     */
   public void stop(String serverHost){
       ChannelFuture future = futureMap.get(serverHost);//获取监听器
       if(future == null){
           log.error("停止服务,服务端：{}，未连接", serverHost);
           return;
       }
       //future.channel().closeFuture();//返回一个新的ChannelFuture，这个新的Future代表了关闭该Channel这一操作的结果。
       future.channel().close();//关闭通道
       EventLoopGroup workerGroup =  workerGroupMap.get(serverHost);//获取工作线程组
       workerGroup.shutdownGracefully();//关闭线程组
       future = null;//移除监听器
       workerGroupMap.remove(serverHost);//移除工作线程组
       cmMap.remove(serverHost);//移除连接管理
       futureMap.remove(serverHost);//移除监听器
       log.info("停止服务,服务端：{}", serverHost);
   }
   /**
    * 创建通道
    * @param ch
    * @throws Exception
    */
   @Override
   protected void initChannel(SocketChannel ch) throws Exception {
       log.info("创建通道");
       ChannelPipeline channelPipeline = ch.pipeline();
       for(InetSocketAddress address : addressSet){
           BaseChannel channel = new DeviceClientChannel(address, ch);
           BaseChannel isExist = (BaseChannel) CacheUtils.get(channel.name());
           if(ObjectUtil.isNull(isExist)){
               BaseClientConnect clientConnect =  cmMap.get(channel.name());//获取连接管理
               if (ObjectUtil.isNotNull(clientConnect)){
                   channelPipeline.addLast(clientConnect);//添加连接管理
               }else {
                   log.info("添加clientConnect失败：{}",clientConnect);
               }
               /**
                * IdleStateHandler
                * 这个类会根据你设置的超时参数的类型和值，
                *               循环去检测channelRead和write方法多久没有被调用了，
                *               如果这个时间超过了你设置的值，那么就会触发对应的事件，read触发read，write触发write，all触发all
                *               如果超时了，则会调用userEventTriggered方法，且会告诉你超时的类型
                * 如果没有超时，则会循环定时检测，除非你将IdleStateHandler移除Pipeline
                */
               channelPipeline.addLast(new IdleStateHandler(0, 0, idleTimeout, TimeUnit.MILLISECONDS));//添加心跳检测
               channelPipeline.addLast(new ChannelInboundHandlerAdapter(){
                   /**
                    * 触发事件
                    * @param ctx
                    * @param evt
                    * @throws Exception
                    */
                   @Override
                   public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
                       //IdleStateEvent在Netty中用于指示通道处于读空闲、写空闲或读写双空闲的状态，常用于实现心跳检测和空闲超时处理。
                       if(evt instanceof IdleStateEvent){
                           SocketAddress remoteAddress = ctx.channel().remoteAddress();
                           log.info("向服务器发送心跳,服务端：{}", remoteAddress.toString());
                           SendUtil.sendData(ctx.channel(), new ClientPingMessageData());
                       } else {
                           super.userEventTriggered(ctx, evt);//;调用父类的方法，以确保其他类型的用户事件也能被正确处理或传递给链中的下一个处理器。
                       }
                   }

               });

               channelPipeline.addLast(new ByteClientMessageEncoder());
               channelPipeline.addLast(new DelimiterMessageDecoder());
               channelPipeline.addLast(new ByteClientMessageDecoder(baseMessageData));
               channelPipeline.addLast(handler);
               channelPipeline.addLast(new ExceptionClientHandler());
           }
       }
   }

   /**
    * 添加接收指定指令的消息监听器
    * @param command 指令类型
    * @param listener 消息监听器
    * @return 上一个消息监听器，如果没有返回null
    */
   public Listener addListener(int command, Listener listener){
       return handler.addListener(command, listener);
   }

   /**
    * 移除接收指定指令的消息监听器
    * @param command 指令类型
    * @return 移除消息监听器，如果没有返回null
    */
   public Listener removeListener(int command){
       return handler.removeListener(command);
   }


}
